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

如何存储VoIP应用中的通话记录?基于Linphone库的技术咨询

Hey there! I see the issue you're facing—your call history vanishes when restarting the VoIP app because right now you're only pulling logs directly from Linphone's in-memory cache. Once the app closes, that memory gets cleared, so those logs are gone for good. Let's fix this by persisting the call history locally using Android's recommended structured storage solution: Room Database. Here's a step-by-step breakdown:

Why Your Current Setup Fails

Looking at your adapter code, every time getCount() runs, you're fetching logs directly from LinphoneManager.getLc().getCallLogs()—this pulls data that exists only in Linphone's runtime memory. When the app restarts, Linphone resets its internal state, so those cached logs are lost. We need to save these logs to a persistent local store so they survive app restarts.

Step 1: Define a Local Data Model for Call History

First, create a data class to represent call history entries that we can store in the database. We'll capture all critical details from Linphone's CallLog:

@Entity(tableName = "call_history")
public class CallHistoryEntry {
    @PrimaryKey(autoGenerate = true)
    private long id;
    private String remoteAddress;
    private int callType; // 0 = incoming, 1 = outgoing, 2 = missed
    private long startTime; // Unix timestamp in milliseconds
    private long duration; // Call duration in seconds

    // Constructor, getters, and setters
    public CallHistoryEntry(String remoteAddress, int callType, long startTime, long duration) {
        this.remoteAddress = remoteAddress;
        this.callType = callType;
        this.startTime = startTime;
        this.duration = duration;
    }

    // Add getters for all fields here
    public String getRemoteAddress() { return remoteAddress; }
    public int getCallType() { return callType; }
    public long getStartTime() { return startTime; }
    public long getDuration() { return duration; }
}

Step 2: Set Up Room Database & DAO

Next, create a Data Access Object (DAO) to handle database operations, then wrap it in a Room database class:

Call History DAO

@Dao
public interface CallHistoryDao {
    @Insert
    void insertCallHistory(CallHistoryEntry entry);

    @Query("SELECT * FROM call_history ORDER BY startTime DESC")
    List<CallHistoryEntry> getAllCallHistory();

    @Query("DELETE FROM call_history")
    void clearAllCallHistory();
}

Room Database Class

@Database(entities = {CallHistoryEntry.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
    public abstract CallHistoryDao callHistoryDao();

    private static volatile AppDatabase INSTANCE;

    public static AppDatabase getInstance(Context context) {
        if (INSTANCE == null) {
            synchronized (AppDatabase.class) {
                if (INSTANCE == null) {
                    INSTANCE = Room.databaseBuilder(
                            context.getApplicationContext(),
                            AppDatabase.class,
                            "voip_app_database"
                    ).build();
                }
            }
        }
        return INSTANCE;
    }
}

Step 3: Save Call Logs to Database in Real-Time

We need to listen for when calls end, then save the corresponding Linphone CallLog to our database. Use Linphone's core listener to capture call state changes:

LinphoneManager.getLc().addListener(new LinphoneCoreListener() {
    @Override
    public void callStateChanged(LinphoneCore lc, LinphoneCall call, LinphoneCall.State state, String message) {
        // Save log when call ends (either normally or with an error)
        if (state == LinphoneCall.State.Released || state == LinphoneCall.State.Error) {
            LinphoneCallLog log = call.getCallLog();
            if (log != null) {
                // Map LinphoneCallLog to our local data model
                int callType;
                if (log.getDir() == LinphoneCallLog.Incoming) {
                    callType = log.getStatus() == LinphoneCallLog.Missed ? 2 : 0;
                } else {
                    callType = 1; // Outgoing call
                }

                CallHistoryEntry entry = new CallHistoryEntry(
                        log.getRemoteAddress().asStringUriOnly(),
                        callType,
                        log.getStartDate(),
                        log.getDuration()
                );

                // Insert into database (always do this on a background thread!)
                new Thread(() -> {
                    AppDatabase.getInstance(getApplicationContext()).callHistoryDao()
                            .insertCallHistory(entry);
                    // Optional: Refresh your adapter here to show the new log immediately
                    mCallHistoryAdapter.refreshCallHistory();
                }).start();
            }
        }
    }
});

Step 4: Modify Your Adapter to Load from Local Database

Update your CallHistoryAdapter to pull data from the Room database instead of Linphone's in-memory cache:

class CallHistoryAdapter extends BaseAdapter {
    private List<CallHistoryEntry> mCallHistoryList;
    private LayoutInflater mInflater;
    private AppDatabase mDb;

    public CallHistoryAdapter(Context context) {
        mInflater = LayoutInflater.from(context);
        mDb = AppDatabase.getInstance(context);
        loadCallHistory(); // Load existing logs when adapter initializes
    }

    // Load logs from database on a background thread
    private void loadCallHistory() {
        new Thread(() -> {
            mCallHistoryList = mDb.callHistoryDao().getAllCallHistory();
            // Refresh UI on main thread
            ((Activity) mInflater.getContext()).runOnUiThread(this::notifyDataSetChanged);
        }).start();
    }

    @Override
    public int getCount() {
        return mCallHistoryList != null ? mCallHistoryList.size() : 0;
    }

    @Override
    public Object getItem(int position) {
        return mCallHistoryList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        View view = null;
        ViewHolder holder = null;

        if (convertView != null) {
            view = convertView;
            holder = (ViewHolder) view.getTag();
        } else {
            view = mInflater.inflate(R.layout.history_cell, parent, false);
            holder = new ViewHolder(view);
            view.setTag(holder);
        }

        if (mCallHistoryList == null || mCallHistoryList.size() <= position) return view;

        CallHistoryEntry entry = mCallHistoryList.get(position);
        // Update UI elements with local data
        holder.remoteNumber.setText(entry.getRemoteAddress());
        
        // Set call type icon
        switch(entry.getCallType()) {
            case 0:
                holder.callTypeIcon.setImageResource(R.drawable.ic_incoming_call);
                break;
            case 1:
                holder.callTypeIcon.setImageResource(R.drawable.ic_outgoing_call);
                break;
            case 2:
                holder.callTypeIcon.setImageResource(R.drawable.ic_missed_call);
                break;
        }

        // Format start time and duration
        holder.callTime.setText(new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault())
                .format(new Date(entry.getStartTime())));
        holder.callDuration.setText(formatDuration(entry.getDuration()));

        return view;
    }

    // Helper to format duration into MM:SS
    private String formatDuration(long seconds) {
        long minutes = seconds / 60;
        long secs = seconds % 60;
        return String.format("%02d:%02d", minutes, secs);
    }

    // Call this to refresh the list when a new log is added
    public void refreshCallHistory() {
        loadCallHistory();
    }

    static class ViewHolder {
        TextView remoteNumber;
        ImageView callTypeIcon;
        TextView callTime;
        TextView callDuration;

        ViewHolder(View view) {
            remoteNumber = view.findViewById(R.id.remote_number);
            callTypeIcon = view.findViewById(R.id.call_type_icon);
            callTime = view.findViewById(R.id.call_time);
            callDuration = view.findViewById(R.id.call_duration);
        }
    }
}

Bonus Tips

  • For better performance, consider using Kotlin Coroutines or AsyncTaskExecutor instead of raw Thread for database operations.
  • Add a "clear history" button by calling clearAllCallHistory() from the DAO.
  • Room automatically handles schema migrations if you need to add new fields to your CallHistoryEntry later.

内容的提问来源于stack exchange,提问作者J.Rashit

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 09:30:01