首页 > 解决方案 > 如何使用 componentWillUnmount()?

问题描述

我是使用 React-Native 的新手,我正在为流媒体应用程序创建音乐播放器,几乎一切正常,它在后台播放,但是当我想切换到另一张专辑或播放列表时,它不会剪切正在播放的歌曲要播放新的,它会同时播放上一首歌曲和新一首歌曲。

它向我显示了这个警告:

警告:无法对未安装的组件执行 React 状态更新。这是一个空操作,但它表明您的应用程序中存在内存泄漏。要解决此问题,请取消 componentWillUnmount 方法中的所有订阅和异步任务。

但是我不知道如何取消所有订阅和异步任务。

这是我的代码。

import {
  StyleSheet,
  TouchableOpacity,
  View,
  Image,
  ImageBackground,
  Slider,
} from "react-native";
import { Title, Text } from "react-native-paper";
import { LinearGradient } from "expo-linear-gradient";
import { Button } from "../components/Button";
import { Audio, Video } from "expo-av";
import firebase from "../utils/firebase";
import "firebase/firestore";
import { Ionicons } from "@expo/vector-icons";

export default function ReproductorAudio(props) {
  const { route } = props;
  const { canciones } = route.params;
  const [duration, setDuration] = useState(0);
  const [totalDuration, setTotalDuration] = useState(0);

  const cancionesPlaylist = canciones;

  console.log(cancionesPlaylist);

  return (
    <ReproductorMusica
      duration={duration}
      cancionesPlaylist={cancionesPlaylist}
      totalDuration={totalDuration}
    />
  );
}

class ReproductorMusica extends React.Component {
  state = {
    isPlaying: false,
    playbackInstance: null,
    currentIndex: 0,
    duration: 0,
    volume: 1.0,
    isBuffering: false,
    isMounted: false,
    totalDuration: 0,
  };

  async componentDidMount() {
    this.isMounted = true;

    try {
      await Audio.setAudioModeAsync({
        allowsRecordingIOS: false,
        interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX,
        playsInSilentModeIOS: true,
        interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DUCK_OTHERS,
        shouldDuckAndroid: true,
        staysActiveInBackground: true,
        playThroughEarpieceAndroid: true,
      });

      this.loadAudio();
    } catch (e) {
      console.log(e);
    }
  }

  async componentWillUnmount() {
    Audio.setAudioModeAsync({
      allowsRecordingIOS: false,
      interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX,
      playsInSilentModeIOS: true,
      interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DUCK_OTHERS,
      shouldDuckAndroid: true,
      staysActiveInBackground: true,
      playThroughEarpieceAndroid: true,
    });
  }

  async loadAudio() {
    const { currentIndex, isPlaying, volume } = this.state;

    try {
      const playbackInstance = new Audio.Sound();
      const source = {
        uri: this.props.cancionesPlaylist[currentIndex].song,
      };

      const status = {
        shouldPlay: isPlaying,
        volume,
      };

      playbackInstance.setOnPlaybackStatusUpdate(this.onPlaybackStatusUpdate);
      await playbackInstance.loadAsync(source, status, false);
      this.setState({ playbackInstance });
    } catch (e) {
      console.log(e);
    }
  }

  onPlaybackStatusUpdate = (status) => {
    this.setState({
      isBuffering: status.isBuffering,
    });
  };

  handlePlayPause = async () => {
    const { isPlaying, playbackInstance } = this.state;
    isPlaying
      ? await playbackInstance.pauseAsync()
      : await playbackInstance.playAsync();

    this.setState({
      isPlaying: !isPlaying,
    });
  };

  handlePreviousTrack = async () => {
    let { playbackInstance, currentIndex } = this.state;
    if (playbackInstance) {
      await playbackInstance.unloadAsync();
      currentIndex < this.props.cancionesPlaylist.length - 1
        ? (currentIndex -= 1)
        : (currentIndex = 0);
      this.setState({
        currentIndex,
      });
      this.loadAudio();
    }
  };

  handleNextTrack = async () => {
    let { playbackInstance, currentIndex } = this.state;
    if (playbackInstance) {
      await playbackInstance.unloadAsync();
      currentIndex < this.props.cancionesPlaylist.length - 1
        ? (currentIndex += 1)
        : (currentIndex = 0);
      this.setState({
        currentIndex,
      });
      this.loadAudio();
    }
  };

  renderFileInfo() {
    const {
      playbackInstance,
      currentIndex,
      duration,
      totalDuration,
    } = this.state;

    return playbackInstance ? (
      <View style={styles.trackInfo}>
        <Image
          style={styles.albumCover}
          source={{
            uri: this.props.cancionesPlaylist[currentIndex].image,
          }}
        />
        <Title style={[styles.trackInfoText, styles.largeText]}>
          {this.props.cancionesPlaylist[currentIndex].name}
        </Title>
        <Title style={[styles.trackInfoText, styles.smallText]}></Title>
        <View style={styles.progressContainer}>
          <Slider
            totalDuration={this.props.cancionesPlaylist[currentIndex].duracion}
            onValueChange={(value) =>
              this.props.cancionesPlaylist[duration](value)
            }
          />
          <View style={styles.durationContainer}>
            <Text style={styles.durationTextLeft}>{duration}</Text>
            <Text style={styles.durationTextRight}>
              -{(totalDuration - duration).toFixed(2)}
            </Text>
          </View>
        </View>
        {/*<Title style={[styles.trackInfoText, styles.smallText]}>
          {this.props.cancionesPlaylist[currentIndex].pista}
        </Title>*/}
      </View>
    ) : null;
  }

  render() {
    const { playbackInstance, currentIndex } = this.state;

    return (
      <ImageBackground
        style={styles.backgroundImage}
        source={{
          uri: this.props.cancionesPlaylist[currentIndex].image,
        }}
        blurRadius={25}
      >
        <View style={styles.container}>
          {this.renderFileInfo()}
          <View style={styles.controls}>
            <TouchableOpacity
              style={styles.control}
              onPress={this.handlePreviousTrack}
            >
              <Ionicons
                name="arrow-back-circle-outline"
                size={48}
                color="#fff"
              />
            </TouchableOpacity>
            <TouchableOpacity
              style={styles.control}
              onPress={this.handlePlayPause}
            >
              {this.state.isPlaying ? (
                <Ionicons name="ios-pause" size={48} color="#fff" />
              ) : (
                <Ionicons name="ios-play-circle" size={48} color="#fff" />
              )}
            </TouchableOpacity>
            <TouchableOpacity
              style={styles.control}
              onPress={this.handleNextTrack}
            >
              <Ionicons
                name="arrow-forward-circle-outline"
                size={48}
                color="#fff"
              />
            </TouchableOpacity>
          </View>
        </View>
      </ImageBackground>
    );
  }
}

const styles = StyleSheet.create({
  backgroundImage: {
    position: "absolute",
    top: 0,
    left: 0,
    bottom: 0,
    right: 0,
  },
  container: {
    flex: 1,
    backgroundColor: "rgba(189,0,0,0.3)",
    alignItems: "center",
    justifyContent: "center",
  },
  albumCover: {
    width: 250,
    height: 250,
    borderRadius: 10,
    borderWidth: 5,
    borderColor: "#fff",
  },
  trackInfo: {
    padding: 40,
    paddingBottom: 0,
    //backgroundColor: "#000",
  },
  trackInfoText: {
    textAlign: "center",
    flexWrap: "wrap",
    color: "#fff",
  },
  largeText: {
    fontSize: 22,
  },
  smallText: {
    fontSize: 16,
  },
  control: {
    margin: 20,
    color: "#fff",
  },
  controls: {
    flexDirection: "row",
    color: "#fff",
  },
  durationContainer: {
    flexDirection: "row",
  },
  durationTextLeft: {
    flex: 0.5,
    textAlign: "left",
    fontSize: 16,
    fontWeight: "bold",
    color: "white",
  },
  durationTextRight: {
    flex: 0.5,
    textAlign: "right",
    fontSize: 16,
    fontWeight: "bold",
    color: "white",
  },
});  ```

标签: javascriptreactjsreact-nativereact-componentreact-component-unmount

解决方案


这似乎是一个架构问题。

如果您在ReproductorMusica并且需要离开该屏幕以转到AlbumScreen您并没有卸载最后一个实例(您已经丢失了,因为您在 ReproductorMusica 中只有一个参考)。

要解决这个问题,您Audio.Sound需要“全球性和独特性”。因此,您可以从任何屏幕访问,而且始终是同一个实例。


推荐阅读