You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android Studio PageSplitter应用启动崩溃求助

Android 长文本分页应用启动崩溃排查

我是Android Studio新手,正在开发一款将长文本拆分为多页的简单应用。找到的旧PageSplitter项目示例在新版Android Studio中出现大量报错,调试后模拟器启动时应用刚打开就终止,恳请帮忙排查问题,相关代码如下:

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="ru.appheads.pagesplitterapp">

<application
    android:allowBackup="true"
    android:dataExtractionRules="@xml/data_extraction_rules"
    android:fullBackupContent="@xml/backup_rules"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/Theme.Pagesplitterapp"
    tools:targetApi="31">
    <activity
        android:name=".MainActivity"
        android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>

</manifest>    

MainActivity.java

package ru.appheads.pagesplitterapp;

import android.os.Bundle;
import android.text.TextPaint;
import android.view.ViewTreeObserver;

import androidx.fragment.app.FragmentActivity;
import androidx.viewpager.widget.ViewPager;


public class MainActivity extends FragmentActivity {
private ViewPager pagesView;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    pagesView = (ViewPager) findViewById(R.id.pages);

    pagesView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            PageSplitter pageSplitter = new PageSplitter(pagesView.getWidth(), pagesView.getHeight(), 1, 0);
            TextPaint textPaint = new TextPaint();
            textPaint.setTextSize(getResources().getDimension(R.dimen.text_size));
            for (int i = 0; i < 1000; i++) {
                pageSplitter.append("Hello, ", textPaint);
                textPaint.setFakeBoldText(true);
                pageSplitter.append("world", textPaint);
                textPaint.setFakeBoldText(false);
                pageSplitter.append("! ", textPaint);
                if ((i + 1) % 100 == 0) {
                    pageSplitter.append("\n", textPaint);
                }
            }
            pagesView.setAdapter(new TextPagerAdapter(getSupportFragmentManager(), pageSplitter.getPages()));
            pagesView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
        }
    });
 }
 }

PageFragment.java

package ru.appheads.pagesplitterapp;

import android.os.Bundle;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.fragment.app.Fragment;

public class PageFragment extends Fragment {
private final static String PAGE_TEXT = "PAGE_TEXT";

public static PageFragment newInstance(CharSequence pageText) {
    PageFragment frag = new PageFragment();
    Bundle args = new Bundle();
    args.putCharSequence(PAGE_TEXT, pageText);
    frag.setArguments(args);
    return frag;
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    CharSequence text = getArguments().getCharSequence(PAGE_TEXT);
    TextView pageView = (TextView) inflater.inflate(R.layout.page, container, false);
    pageView.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension(R.dimen.text_size));
    pageView.setText(text);
    return pageView;
}
}    

PageSplitter.java

package ru.appheads.pagesplitterapp;

import android.graphics.Typeface;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.TextPaint;
import android.text.style.StyleSpan;

import java.util.ArrayList;
import java.util.List;

public class PageSplitter {
private final int pageWidth;
private final int pageHeight;
private final float lineSpacingMultiplier;
private final int lineSpacingExtra;
private final List<CharSequence> pages = new ArrayList<CharSequence>();
private SpannableStringBuilder currentLine = new SpannableStringBuilder();
private SpannableStringBuilder currentPage = new SpannableStringBuilder();
private int currentLineHeight;
private int pageContentHeight;
private int currentLineWidth;
private int textLineHeight;

public PageSplitter(int pageWidth, int pageHeight, float lineSpacingMultiplier, int lineSpacingExtra) {
    this.pageWidth = pageWidth;
    this.pageHeight = pageHeight;
    this.lineSpacingMultiplier = lineSpacingMultiplier;
    this.lineSpacingExtra = lineSpacingExtra;
}

public void append(String text, TextPaint textPaint) {
    textLineHeight = (int) Math.ceil(textPaint.getFontMetrics(null) * lineSpacingMultiplier + lineSpacingExtra);
    String[] paragraphs = text.split("\n", -1);
    int i;
    for (i = 0; i < paragraphs.length - 1; i++) {
        appendText(paragraphs[i], textPaint);
        appendNewLine();
    }
    appendText(paragraphs[i], textPaint);
}

private void appendText(String text, TextPaint textPaint) {
    String[] words = text.split(" ", -1);
    int i;
    for (i = 0; i < words.length - 1; i++) {
        appendWord(words[i] + " ", textPaint);
    }
    appendWord(words[i], textPaint);
}

private void appendNewLine() {
    currentLine.append("\n");
    checkForPageEnd();
    appendLineToPage(textLineHeight);
}

private void checkForPageEnd() {
    if (pageContentHeight + currentLineHeight > pageHeight) {
        pages.add(currentPage);
        currentPage = new SpannableStringBuilder();
        pageContentHeight = 0;
    }
}

private void appendWord(String appendedText, TextPaint textPaint) {
    int textWidth = (int) Math.ceil(textPaint.measureText(appendedText));
    if (currentLineWidth + textWidth >= pageWidth) {
        checkForPageEnd();
        appendLineToPage(textLineHeight);
    }
    appendTextToLine(appendedText, textPaint, textWidth);
}

private void appendLineToPage(int textLineHeight) {
    currentPage.append(currentLine);
    pageContentHeight += currentLineHeight;
    currentLine = new SpannableStringBuilder();
    currentLineHeight = textLineHeight;
    currentLineWidth = 0;
}

private void appendTextToLine(String appendedText, TextPaint textPaint, int textWidth) {
    currentLineHeight = Math.max(currentLineHeight, textLineHeight);
    currentLine.append(renderToSpannable(appendedText, textPaint));
    currentLineWidth += textWidth;
}

public List<CharSequence> getPages() {
    List<CharSequence> copyPages = new ArrayList<CharSequence>(pages);
    SpannableStringBuilder lastPage = new SpannableStringBuilder(currentPage);
    if (pageContentHeight + currentLineHeight > pageHeight) {
        copyPages.add(lastPage);
        lastPage = new SpannableStringBuilder();
    }
    lastPage.append(currentLine);
    copyPages.add(lastPage);
    return copyPages;
}

private SpannableString renderToSpannable(String text, TextPaint textPaint) {
    SpannableString spannable = new SpannableString(text);
    if (textPaint.isFakeBoldText()) {
        spannable.setSpan(new StyleSpan(Typeface.BOLD), 0, spannable.length(), 0);
    }
    return spannable;
 }
 }

TextPagerAdapter.java

package ru.appheads.pagesplitterapp;

import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;

import java.util.List;

public class TextPagerAdapter extends FragmentPagerAdapter {
private final List<CharSequence> pageTexts;

public TextPagerAdapter(FragmentManager fm, List<CharSequence> pageTexts) {
    super(fm);
    this.pageTexts = pageTexts;
}

@Override
public Fragment getItem(int i) {
    return PageFragment.newInstance(pageTexts.get(i));
}

@Override
public int getCount() {
    return pageTexts.size();
}
}    

activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context="ru.appheads.pagesplitterapp.MainActivity">

<androidx.viewpager2.widget.ViewPager2 xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/pages"
    android:overScrollMode="never"
    android:layout_height="match_parent"
    android:layout_width="match_parent">
</androidx.viewpager2.widget.ViewPager2>
</RelativeLayout>    

page.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:lineSpacingMultiplier="1"
android:lineSpacingExtra="0sp">
</TextView>    

styles.xml

<resources>
<style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
</style>
</resources>

build.gradle(app)

plugins {
id 'com.android.application'
}

android {
compileSdk 32

defaultConfig {
    applicationId "ru.appheads.pagesplitterapp"
    minSdk 21
    targetSdk 32
    versionCode 1
    versionName "1.0"
    testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
    release {
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
}
compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
}
}

dependencies {
implementation 'androidx.appcompat:appcompat:1.5.0'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.viewpager2:viewpager2:1.0.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}       

崩溃原因及修复方案

1. ViewPager与ViewPager2类型不匹配

布局文件用的是ViewPager2,但代码里声明的是旧版ViewPager,强转时直接崩溃。

  • 修复:把MainActivity里的ViewPager改为ViewPager2,移除强制类型转换:
    private ViewPager2 pagesView;
    // ...
    pagesView = findViewById(R.id.pages);
    

2. 适配器不兼容ViewPager2

TextPagerAdapter继承的FragmentPagerAdapter是旧版ViewPager的适配器,ViewPager2需要用FragmentStateAdapter。

  • 修复:重写适配器:
    package ru.appheads.pagesplitterapp;
    
    import androidx.fragment.app.Fragment;
    import androidx.fragment.app.FragmentActivity;
    import androidx.viewpager2.adapter.FragmentStateAdapter;
    
    import java.util.List;
    
    public class TextPagerAdapter extends FragmentStateAdapter {
        private final List<CharSequence> pageTexts;
    
        public TextPagerAdapter(FragmentActivity activity, List<CharSequence> pageTexts) {
            super(activity);
            this.pageTexts = pageTexts;
        }
    
        @Override
        public Fragment createFragment(int position) {
            return PageFragment.newInstance(pageTexts.get(position));
        }
    
        @Override
        public int getItemCount() {
            return pageTexts.size();
        }
    }
    
    同时修改MainActivity中设置适配器的代码:
    pagesView.setAdapter(new TextPagerAdapter(MainActivity.this, pageSplitter.getPages()));
    

3. PageSplitter字体度量计算错误

textPaint.getFontMetrics(null)会返回0,因为传入null无法获取有效字体参数,导致行高计算异常。

  • 修复:创建FontMetrics对象传入:
    Paint.FontMetrics metrics = new Paint.FontMetrics();
    textPaint.getFontMetrics(metrics);
    textLineHeight = (int) Math.ceil((metrics.descent - metrics.ascent) * lineSpacingMultiplier + lineSpacingExtra);
    

4. 主题不兼容

旧版android:Theme.Holo.Light.DarkActionBar与AndroidX组件存在兼容性问题。

  • 修复:替换为AndroidX兼容主题:
    <resources>
        <style name="Theme.Pagesplitterapp" parent="Theme.MaterialComponents.Light.DarkActionBar">
            <item name="colorPrimary">@color/purple_500</item>
            <item name="colorPrimaryVariant">@color/purple_700</item>
            <item name="colorOnPrimary">@color/white</item>
            <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
        </style>
    </resources>
    

5. 缺失资源检查

确保res/values/dimens.xml中存在text_size定义:

<dimen name="text_size">16sp</dimen>

若未创建data_extraction_rules.xml和backup_rules.xml,可移除AndroidManifest.xml中对应的属性,避免资源找不到的错误。

内容的提问来源于stack exchange,提问作者user2430364

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 06:05:18